package com.xy.job.executor.core.job

import com.xy.job.core.api.request.JobFeedbackParam
import com.xy.job.core.exception.BizException
import com.xy.job.executor.core.client.IAdminClient
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withTimeout
import org.slf4j.LoggerFactory

internal class Job(
    val jobContext: JobContext,
    val adminClient: IAdminClient,
) {

    private val log = LoggerFactory.getLogger(Job::class.java)

    suspend fun execute() {
        val jobResult = doExecute()
        JobRegistry.remove(jobContext.jobInstanceId)
        destroyScriptProcess()
        feedback(jobResult)
    }

    private suspend fun doExecute(): JobResult {
        return try {
            log.info("ready to execute jobInstance, jobInstanceId={}", jobContext.jobInstanceId)
            val jobHandler = JobHandlerRegistry.find(jobContext.jobHandlerName)
            if (jobContext.timeout > 0) {
                withTimeout(jobContext.timeout) {
                    runInterruptible {
                        jobHandler.handle(jobContext)
                    }
                }
            } else {
                runInterruptible {
                    jobHandler.handle(jobContext)
                }
            }
        } catch (e: Throwable) {
            log.error("execute job failed, msg={}", e.message, e)
            val message: String = when (e) {
                is BizException -> e.message ?: e.stackTraceToString()
                is InterruptedException -> "job is terminated manually"
                is TimeoutCancellationException -> "job is terminated due to timeout, timeout period is ${jobContext.timeout}"
                else -> e.stackTraceToString()
            }
            JobResult.failed(message)
        }
    }

    private fun destroyScriptProcess() {
        if (jobContext.process != null) {
            synchronized(jobContext.process!!) {
                jobContext.process!!.destroy()
            }
        }
    }

    private fun feedback(jobResult: JobResult) {
        val callbackParam = JobFeedbackParam(
            jobInstanceId = jobContext.jobInstanceId,
            jobId = jobContext.jobId,
            jobStatus = jobResult.jobStatus,
            message = jobResult.message,
        )
        val result = adminClient.feedback(callbackParam)
        if (result.success) {
            log.info(
                "the result of jobInstance feedback success: instanceId={}, jobName={}",
                jobContext.jobId,
                jobContext.jobName
            )
        } else {
            log.error(
                "the result of jobInstance feedback fail: instanceId={}, jobName={}, msg={}",
                jobContext.jobId,
                jobContext.jobName,
                result.message,
            )
        }
    }
}